var assert=require('./assertions');
var log=require('logging');
var eyes=require('eyes');

var scope=require('./scope');
var system=require('./native');

var activateMethod=function(method, args)
{
  var argScope={};
  var x;
  var param;
  var arg;
  var activeScope;
  var active;

  assert.exists(method);
  assert.exists(method.params);

  log('activateMethod method', method.code);
  log('method.params: ', method.params);
  log('args:', args.length);

  log('0');

  log('1');

  if(method.params.length!=args.length) // FIXME - type error
  {
  }

  log('2');

  for(x=0; x<method.params.length; x++)
  {
    param=method.params[x];
    arg=args[x];
    argScope[param]=arg;
  }

  log('3');

  activeScope=scope.override(method.scope, argScope);
  log('activeScope:', scope.pp(activeScope));

  log('4');

  active={'scope': activeScope, 'type': method.type, 'code': method.code}

  log('returning: ', Object.keys(active));

  return active;
};

var resolve=function(argScope, arg)
{
  var type=arg[0];
  var value=arg[1];
  var fname;
  var f;
  var fargs;
  var to;

  log('resolve:', arg);

  if(type=='literal')
  {
    return arg;
  }
  else if(type=='block')
  {
    log('! found a bock:', scope.pp(argScope));
    return ['activeBlock', argScope, arg];
  }
  else if(type=='symref')
  {
//    log('dereferencing', value, scope.pp(argScope), argScope[value]);
    log('dereferencing', value, scope.pp(argScope));
//    log('scope:', scope.pp(argScope));

    assert.exists(argScope[value]);

    return argScope[value];
  }
  else if(type=='func')
  {
    fname=value[0];
    fargs=value[1];
    f=argScope[fname];
    log('nested call:', fname, fargs);
    log('argScope: ', scope.pp(argScope));

    assert.exists(f);

    return call(f, argScope, fargs);
  }
  else
  {
    log('unknown type: '+type);
  }
};

var map=function(l, f)
{
  var ps=[];
  var x;
  var results=[];

  for(x=0; x<l.length; x++)
  {
    results.push(f(l[x]));
  }

  return results;
};

var call=function(method, callerContext, args)
{
  var err;
  var fargs;

  if(method===null)
  {
    throw "Null method";
  }

  log('call:', scope.pp(method), method.code, method.params, args);
  log('call:', Object.keys(callerContext).length);

  if(args.length>0)
  {
    log('unwinding', args);
    fargs=map(args, function(arg, p) {return resolve(callerContext, arg);});
    log('done unwinding', fargs.length);
  }
  else
  {
    fargs=[];
  }

  return simpleCall(method, callerContext, fargs);
};

var simpleCall=function(method, callerContext, args)
{
  log('simpleCall:', method.code, method.params, args.length);
  log('simpleCall:', Object.keys(callerContext).length);

  var active=activateMethod(method, args);
  log('active: '+active);

  var type;
  var methodName;
  var f;
  var result;
  var fargs;
  var fresult;
  var rtype;
  var rmethod;
  var rargs;
  var rto;

  if(active.type=='native')
  {
    methodName=active.code;
    log('calling native', methodName);
    f=system[methodName];
    log('f:', f);
    log('active.scope:', scope.pp(active.scope));
    result=f(active.scope);
    log('native result:', result);
  }
  else if(active.type=='method')
  {
    log('calling method', active.code);
    type=active.code[0];
    methodName=active.code[1][0];
    fargs=active.code[1][1];

    if(type!='func') // FIXME - type error
    {
      console.log('not func');
    }
    else
    {
      log('activating recursive method', methodName, scope.pp(active.scope));
      f=scope.activate(active.scope, [methodName], {});
      log('f:', f.code);
      if(f===null)
      {
	throw "Null recursive method";
      }
//      result=call(method.scope, f, fargs, to);
      result=call(f, active.scope, fargs);
    }
  }
  else
  {
    log('unknown type:', active.type);
    return null;
  }

  if(result===undefined || result===null)
  {
    return null;
  }

  rtype=result[0];
  if(rtype=='comp')
  {
    rmethod=result[1];
    rargs=result[2];
    log('result was comp, call it', rmethod.code, rargs, scope.pp(rmethod.scope));

    return simpleCall(rmethod, rmethod.scope, rargs); // FIXME - What is the correct callerContext here?
  }
  else
  {
    return result;
  }
};

exports.callMethod=function(method, callerContext, args)
{
  log('top-level call:', method.params, Object.keys(callerContext).length, args);
  call(method, callerContext, args);
}
